/* 算法实现模板 */
import AlgorithmBase from "./AlgorithmBase";
import {toKatexString,toVector} from "../../../common/Vector";
import 'katex/dist/katex.min.css';
import { showMathStringInTable } from "../../../visual/Table2D";
import NumpyJS from 'jsnumpy';
import { mdRender } from "../../../common/Common";



class EM extends AlgorithmBase{
  constructor(){
    super('EM');
    this.descriptionUrl = `${this.homepage}/data/slm/ch9/9.1/readme.md`;
    this.datasetUrl = `${this.homepage}/data/slm/ch9/9.1/dataset.json`;
  }
  init(){ 
    this.reset();
    this.initTrainDS();
  }
  
  reset(){
    // this.dataset = undefined;
    // this.trainDS = undefined;
    this.clear();
    this.initDescription();
    this.updateModel();
  }
  
  loadModel(json){
    if(json.TrainDS){
      this.trainDSList = [];
      if(json.TrainDS instanceof Array){
        for(let i = 0, il = json.TrainDS.length; i < il; i++){
          const newTrain = json.TrainDS[i];
          newTrain.T = toVector(newTrain.T, true);
          this.trainDSList.push(newTrain);
        }
        this.trainDS = this.trainDSList[0];
      }
      else{
        this.trainDS = json.TrainDS.x;
      }
    }
    this.o = this.trainDS;
    this.model = json.Model.theta;
    let katexString = String.raw`有ABC三枚硬币,单次投掷出现正面的概率分别为π、p、q。利用这三枚硬币进行如下实验:

    1、第一次先投掷A,若出现正面则投掷B,否则投掷C
    
    2、记录第二次投掷的硬币出现的结果,正面记作1,反面记作0
    
       独立重复1和2十次,产生如下观测结果：
                 [`;
    for(let i = 0, il = this.o.length; i < il; i++){
      katexString += String.raw`${this.o[i]}`;;
      if(i !== il - 1){
        katexString += '],[';
      }
    }
    katexString += String.raw`]`; 
    mdRender(`${katexString}`, this.inputDiv);
    mdRender(`\π: ${this.model[0]} \p: ${this.model[1]}\ \q: ${this.model[2]} `, this.modelDiv);

  }

  initDescription(){
    fetch(this.descriptionUrl).then((response)=>{
      return response.text();
    }).then((txt)=>{

      this.reandMD(txt, this.descriptionDiv)
    })
  }
  
  initTrainDS(){
    this.prepare(this.datasetUrl);
  }


  // 准备数据训练数据
  prepare(url){
    fetch(url).then((response)=>{
      return response.json();
    }).then((json)=>{
      this.trainDS = this.loadModel(json);
      this.updateModel();
    })
  }

  openFileSelector(){
    this.fileSelector.click();
  }

  // 获取文件路径加载
  handleFileChange(event){
    const files = event.target.files;

    for(let i = 0, il = files.length; i < il; i++){
      let reader = new FileReader();
      reader.onload = (e) => {      
        this.prepare(e.target.result);
        if(i === files.length - 1){
          event.target.value = '';
        }
      };
      reader.readAsDataURL(files[i])
    }
  }

   // 输入数据UI
   initInputUI(){

    // 文件选择器
    const fileSelector      = document.createElement('input');
    fileSelector.type       = 'file';
    fileSelector.hidden     = true;
    fileSelector.onchange   = this.handleFileChange.bind(this);   
    
    this.addButton(this.domElementList.input, "获取服务器数据", this.initTrainDS.bind(this));
    this.addButton(this.domElementList.input, "使用本地数据", this.openFileSelector.bind(this));
    this.domElementList.input.appendChild(fileSelector);
    this.addButton(this.domElementList.input, "训练", this.train.bind(this));
    this.labelTrainTips = this.addLabel(this.domElementList.input, '');
    this.inputDiv       = this.addDiv(this.domElementList.input);
    this.fileSelector  = fileSelector;
  }


  // 输出UI
  initOutputUI(){
    this.outputDiv   = this.addDiv(this.domElementList.output);
    this.trainLogDiv = this.addDiv(this.domElementList.output);
    
    
  }
  em_single(theta,o){
    var pi = theta[0];
    var h1 = theta[1];
    var h2 = theta[2];
    var new_pi = 0;
    var new_h1 = 0;
    var new_h2 = 0;
    var pb = [];
    var head = [];
    for(var i=0;i<o.length;i++){
      var t = o[i].length;
      var cnt = NumpyJS.sum(o[i]);
      head.push(cnt);
    }
    for(var j=0;j<o.length;j++){
      var t = o[j].length;
      var heads = NumpyJS.sum(o[j]);
      var tails = t-heads;
      var u = (pi*Math.pow(h1,heads)*Math.pow(1-h1,tails))/
      (pi*Math.pow(h1,heads)*Math.pow(1-h1,tails)+(1-pi)*Math.pow(h2,heads)*Math.pow(1-h2,tails));
      pb.push(u);
    }
    var l = pb.length;
    new_pi = NumpyJS.sum(pb)/l;
    for(var k=0;k<l;k++){
      new_h1 += pb[k]*head[k]/t;
      new_h2 += (1-pb[k])*head[k]/t;
    }
    new_h1 = new_h1/NumpyJS.sum(pb);
    new_h2 = new_h2/(l-NumpyJS.sum(pb));
    return [new_pi,new_h1,new_h2];
  }

  em(theta,y,tol){
    var j=0;
    var process = [];
    while(j<1000){
      var new_theta = this.em_single(theta,y);
      var change = NumpyJS.abs(new_theta[0]-theta[0]);
      if(change<tol){
        break;
      }
      else{
        theta = new_theta;
        j = j+1;
        process.push(new_theta);
        console.log("迭代后的参数为",new_theta);
      }
    }
    console.log(process);
    return [new_theta,process]
  }



  train(){
    var o = NumpyJS.copy(this.o);
    var theta = this.model;
    // var t = theta.length;
    console.log("初始参数为",theta);
    var theta_process = this.em(theta,o,0.00001);
    var process = theta_process[1];
    var table = [];
    table.push([String.raw`\textbf{迭代次数}`,String.raw`\textbf{π}`,String.raw`\textbf{p}`,String.raw`\textbf{q}`]);
    for(var i=0;i<process.length;i++){
      table.push([]);
      table[i+1][0] = String.raw`${i}`;
      table[i+1][1] = String.raw`${process[i][0]}`;
      table[i+1][2] = String.raw`${process[i][1]}`;
      table[i+1][3] = String.raw`${process[i][2]}`;
    }
    showMathStringInTable(this.outputDiv, table)



  }
  updateModel(){


  }

}



export default EM;